/*
 * Copyright (C) 2024 HiHope Open Source Organization.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { createOrGet, Log } from '@ohos/common';
import oneConnectShare from '@ohos.oneConnect.fileShare';
import { BusinessError, Callback } from '@kit.BasicServicesKit';

export enum AuthStateMsg {
  MSG_PIN_CODE_ERROR = 0,
  MSG_PIN_CODE_SUCCESS = 1,
  MSG_CANCEL_PIN_CODE_SHOW = 2,
  MSG_CANCEL_PIN_CODE_INPUT = 3,
  MSG_DOING_AUTH = 4,
  MSG_CANCEL_CONFIRM_SHOW = 5,
  MSG_SINK_CANCEL_AUTH = 6,
}

export interface DiscoverSuccessData {
  device: oneConnectShare.ShareDeviceInfo
};

export interface DiscoverFailureData {
  reason: number
};

export interface ConnectionChangeData {
  action: oneConnectShare.ShareDeviceStateChange,
  device: oneConnectShare.ShareDeviceInfo
};

export interface SendChangeData {
  state: oneConnectShare.SendState;
  info: oneConnectShare.SendInfo;
};

export interface AuthStateMsgData {
  uiStateMsg: AuthStateMsg;
}

export type DiscoverSuccessListener = Callback<DiscoverSuccessData>;

export type DiscoverFailureListener = Callback<DiscoverFailureData>;

export type ConnectionChangeListener = Callback<ConnectionChangeData>;

export type SendChangeListener = Callback<SendChangeData>;

export type AuthStateChangeListener = Callback<AuthStateMsgData>;

/**
 * 对应sendChange回调上报的 SendInfo 中的 type 字段
 */
export enum CurrentDeviceType {
  None = -1,
  Source = 0,
  Sink = 1
};

const TAG = 'ShareManager';

const CREATE_MANAGER_RETRY_TIMEOUT_MS = 1000;

export class ShareManager {
  // source端和sink端同属于同一个进程,而其监听后的执行逻辑却各自不同,因此使用listener数组的方式将两端的处理流程解耦
  private discoverSuccessListeners: Array<DiscoverSuccessListener> = [];
  private discoverFailureListeners: Array<DiscoverFailureListener> = [];
  private connectionChangeListeners: Array<ConnectionChangeListener> = [];
  private sendChangeListeners: Array<SendChangeListener> = [];
  private authStateChangeListeners: Array<AuthStateChangeListener> = [];

  private inited: boolean = false;
  private shareManager: oneConnectShare.ShareManager;
  private registered: boolean = false;

  private localDeviceType: SubscribedAbstractProperty<CurrentDeviceType> = AppStorage.setAndLink('CurrentDeviceType', CurrentDeviceType.None);

  init(bundleName: string) {
    Log.debug(TAG, `init in bundleName = ${bundleName}`);
    if (!this.inited) {
      Log.info(TAG, 'do init');
      this.initCallback(bundleName);
      this.inited = true;
      Log.info(TAG, `do init finish, this.shareManager = ${this.shareManager}`);
    }
  }

  initCallback = (bundleName: string) => {
    try {
      this.shareManager = oneConnectShare?.createShareManager(bundleName);
      if (bundleName == 'com.ohos.oneconnect.share') {
        this.registerListeners();
      }
      Log.info(TAG, `do init success, this.shareManager = ${this.shareManager}`);
    } catch (err) {
      Log.error(TAG, `createShareManager err: ${err}, ${JSON.stringify(err)}`);
      setTimeout(() => {
        this.initCallback(bundleName);
      }, CREATE_MANAGER_RETRY_TIMEOUT_MS);
    }
  }

  release() {
    Log.debug(TAG, 'release in');
    if (this.inited) {
      Log.info(TAG, 'do release');
      this.unregisterListeners();
      try {
        oneConnectShare?.releaseShareManager(this.shareManager);
      } catch (err) {
        Log.error(TAG, `releaseShareManager err: ${err}, ${JSON.stringify(err)}`);
      }
      this.shareManager = undefined;
      this.inited = false;
      Log.info(TAG, 'do release success');
    }
  }

  /**
   * Sink端开启文件分享能力，启动设备文件分享BLE广播，使设备可以被文件分享发现。
   */
  enableShare() {
    Log.info(TAG, 'enableShare in');
    try {
      this.shareManager?.enableShare();
    } catch (err) {
      Log.error(TAG, `enableShare err: ${err}, ${JSON.stringify(err)}`);
    }
  }

  /**
   * Sink端关闭文件分享能力，停止设备文件分享BLE广播，设备无法被文件分享发现。
   */
  disableShare() {
    Log.info(TAG, 'disableShare in');
    try{
      this.shareManager?.disableShare();
    } catch (err) {
      Log.error(TAG, `disableShare err: ${err}, ${JSON.stringify(err)}`);
    }
  }

  /**
   * Sink端获取文件分享状态。
   * @returns 文件分享状态
   */
  getShareStatus(): boolean {
    Log.info(TAG, 'getShareStatus in');
    let result = false;
    try {
      result = this.shareManager?.getShareStatus() || false;
    } catch (err) {
      Log.error(TAG, `getShareStatus err: ${err}, ${JSON.stringify(err)}`);
    }
    Log.info(TAG, `getShareStatus result = ${result}`);
    return result;
  }

   /**
   * Source端开始扫描文件分享设备广播。
   */
  startScan() {
    Log.info(TAG, 'startScan in');
    try {
      this.shareManager?.startScan();
    } catch (err) {
      Log.error(TAG, `startScan err: ${err}, ${JSON.stringify(err)}`);
    }
  }

  /**
   * Source端停止扫描文件分享设备广播。
   */
  stopScan() {
    Log.info(TAG, 'stopScan in');
    try {
      this.shareManager?.stopScan();
    } catch (err) {
      Log.error(TAG, `stopScan err: ${err}, ${JSON.stringify(err)}`);
    }
  }

  /**
   * 获取已连接设备列表。
   */
  getAvailableDeviceList(): Array<oneConnectShare.ShareDeviceInfo> {
    Log.info(TAG, 'getAvailableDeviceList in');
    let result = [];
    try {
      result = this.shareManager?.getAvailableDeviceList();
    } catch (err) {
      Log.error(TAG, `getAvailableDeviceList err: ${err}, ${JSON.stringify(err)}`);
    }
    Log.error(TAG, `availableDeviceList: ${JSON.stringify(result)}`);
    return result;
  }

  /**
   * Source端连接对端设备。
   * @param deviceId 对端deviceId
   * @param bindParam 连接参数: 包含 "bindType", "targetPkgName",  "fileInfo" 等字段
   * @returns 对端device信息
   */
  connect(deviceId: string, bindParam: { [key: string]: Object }): Promise<string> {
    Log.info(TAG, `connect in, deviceId = ${deviceId}, bindParam = ${JSON.stringify(bindParam)}`);
    let promise: Promise<string> = new Promise<string>((resolve, reject) => {
      try {
        this.shareManager?.connect(deviceId, bindParam, (error: BusinessError, result: { deviceId: string }) => {
          if (error) {
            Log.error(TAG, `connect callback failure: ${error}, ${JSON.stringify(error)}`);
            reject(error);
          } else {
            Log.info(TAG, `connect success: ${JSON.stringify(result)}`);
            resolve(result.deviceId);
          }
        })
      } catch(error) {
        Log.error(TAG, `connect failure: ${error}, ${JSON.stringify(error)}`);
        reject(error);
      }
    });
    return promise;
  }

  /**
   * Source端断开对端设备。
   * @param deviceId 对端deviceId
   */
  disconnect(deviceId: string) {
    Log.info(TAG, `disconnect in, deviceId = ${deviceId}`);
    try {
      this.shareManager?.disconnect(deviceId);
    } catch (err) {
      Log.error(TAG, `disconnect err: ${err}, ${JSON.stringify(err)}`);
    }
  }

  /**
   * Sink端确认接收文件发送会话，准备开始文件接收流程
   */
  confirmConnect() {
    Log.info(TAG, 'confirmConnect in');
    try {
      this.shareManager?.confirmConnect();
    } catch (err) {
      Log.error(TAG, `confirmConnect err: ${err}, ${JSON.stringify(err)}`);
    }
  }

  /**
   * Sink拒绝文件发送会话，终止文件分享流程。
   */
  refuseConnect() {
    Log.info(TAG, 'refuseConnect in');
    try {
      this.shareManager?.refuseConnect();
    } catch (err) {
      Log.error(TAG, `refuseConnect err: ${err}, ${JSON.stringify(err)}`);
    }
  }

  /**
   * Source端主动取消文件发送会话，终止文件发送流程。
   * @param deviceId: sink端id
   */
  cancelSendFile(deviceId: string) {
    Log.info(TAG, `cancelSendFile in, deviceId = ${deviceId}`);
    try {
      this.shareManager?.cancelSendFile(deviceId);
    } catch (err) {
      Log.error(TAG, `cancelSendFile err: ${err}, ${JSON.stringify(err)}`);
    }
  }

  /**
   * Sink端主动取消文件接收会话，终止文件接收流程。
   * @param deviceId: source端id
   */
  abortReceiveFile(deviceId: string) {
    Log.info(TAG, `abortReceiveFile in, deviceId = ${deviceId}`);
    try {
      this.shareManager?.abortReceiveFile(deviceId);
    } catch (err) {
      Log.error(TAG, `abortReceiveFile err: ${err}, ${JSON.stringify(err)}`);
    }
  }

  /**
   * Source端发送文件内容。
   * @param deviceId: sink端id
   * @param files: 文件列表信息
   */
  sendFile(deviceId: string, type: oneConnectShare.ShareType, files: Array<oneConnectShare.ShareFileInfo>) {
    Log.info(TAG, `sendFile in, deviceId = ${deviceId}, files = ${JSON.stringify(files)}`);
    try {
      this.shareManager?.sendFile(deviceId, type, files);
    } catch (err) {
      Log.error(TAG, `sendFile err: ${err}, ${JSON.stringify(err)}`);
    }
  }

  /**
   * 注册设备发现成功事件。
   * @param listener
   */
  registerDiscoverSuccess(listener: DiscoverSuccessListener) {
    Log.info(TAG, 'registerDiscoverSuccess in');
    this.addListenerSafely(listener, this.discoverSuccessListeners);
  }

  /**
   * 取消注册设备发现成功事件。
   * @param listener
   */
  unregisterDiscoverSuccess(listener: DiscoverSuccessListener) {
    Log.info(TAG, 'unregisterDiscoverSuccess in');
    this.removeListenerSafely(listener, this.discoverSuccessListeners);
  }

  /**
   * 注册设备发现失败事件。
   * @param listener
   */
  registerDiscoverFailure(listener: DiscoverFailureListener) {
    Log.info(TAG, 'registerDiscoverFailure in');
    this.addListenerSafely(listener, this.discoverFailureListeners);
  }

  /**
   * 取消注册设备发现失败事件。
   * @param listener
   */
  unregisterDiscoverFailure(listener: DiscoverFailureListener) {
    Log.info(TAG, 'unregisterDiscoverFailure in');
    this.removeListenerSafely(listener, this.discoverFailureListeners);
  }

  /**
   * 注册连接状态改变事件。
   * @param listener
   */
  registerConnectionChange(listener: ConnectionChangeListener) {
    Log.info(TAG, 'registerConnectionChange in');
    this.addListenerSafely(listener, this.connectionChangeListeners);
  }

  /**
   * 取消注册连接状态改变事件。
   * @param listener
   */
  unregisterConnectionChange(listener: ConnectionChangeListener) {
    Log.info(TAG, 'unregisterConnectionChange in');
    this.removeListenerSafely(listener, this.connectionChangeListeners);
  }

  /**
   * 注册发送状态更新事件
   * @param listener
   */
  registerSendChange(listener: SendChangeListener) {
    Log.info(TAG, 'registerSendChange in');
    this.addListenerSafely(listener, this.sendChangeListeners);
  }

  /**
   * 取消注册发送状态更新事件
   * @param listener
   */
  unregisterSendChange(listener: SendChangeListener) {
    Log.info(TAG, 'unregisterSendChange in');
    this.removeListenerSafely(listener, this.sendChangeListeners);
  }

  /**
   * 注册pin码认证状态(Sink端)更新事件
   * @param listener
   */
  registerAuthStateChange(listener: AuthStateChangeListener) {
    Log.info(TAG, 'registerAuthStateChange in');
    this.addListenerSafely(listener, this.authStateChangeListeners);
  }

  /**
   * 取消注册pin码认证状态(Sink端)更新事件
   * @param listener
   */
  unregisterAuthStateChange(listener: AuthStateChangeListener) {
    Log.info(TAG, 'unregisterAuthStateChange in');
    this.removeListenerSafely(listener, this.authStateChangeListeners);
  }

  isLocalSource(): boolean {
    return this.localDeviceType.get() == CurrentDeviceType.Source;
  }

  isLocalSink(): boolean {
    return this.localDeviceType.get() == CurrentDeviceType.Sink;
  }

  isLocalFree(): boolean {
    return this.localDeviceType.get() == CurrentDeviceType.None;
  }

  setLocalDeviceType(localDeviceType: CurrentDeviceType) {
    Log.info(TAG, `setLocalDeviceType ${localDeviceType}`);
    this.localDeviceType.set(localDeviceType);
  }

  private addListenerSafely<T>(listener: T, listeners: Array<T>) {
    Log.info(TAG, 'addListenerSafely in');
    if (listeners.indexOf(listener) < 0) {
      listeners.push(listener);
    }
    Log.info(TAG, `addListenerSafely out, listener's length = ${listeners.length}`);
  }

  private removeListenerSafely<T>(listener: T, listeners: Array<T>) {
    Log.info(TAG, 'removeListenerSafely in');
    const position = listeners.indexOf(listener);
    if (position >= 0) {
      listeners.splice(position, 1);
    }
    Log.info(TAG, `removeListenerSafely out, listener's length = ${listeners.length}`);
  }

  private registerListeners() {
    Log.info(TAG, 'registerListeners in');
    if (this.registered) {
      Log.warn(TAG, 'already registered in registerListeners');
      return;
    }
    try {
      this.shareManager?.on('discoverSuccess', (deviceChangeData: DiscoverSuccessData) => {
        Log.info(TAG, `discoverSuccess, data = ${JSON.stringify(deviceChangeData)}`);
        this.discoverSuccessListeners.forEach(listener => {
          listener && listener(deviceChangeData);
        })
      })
      this.shareManager?.on('discoverFailure', (deviceChangeData: DiscoverFailureData) => {
        Log.info(TAG, `discoverFailure, data = ${JSON.stringify(deviceChangeData)}`);
        this.discoverFailureListeners.forEach(listener => {
          listener && listener(deviceChangeData);
        })
      })
      this.shareManager?.on('connectionChange', (connectionChangeData: ConnectionChangeData) => {
        Log.info(TAG, `connectionChange, data = ${JSON.stringify(connectionChangeData)}`);
        this.connectionChangeListeners.forEach(listener => {
          listener && listener(connectionChangeData);
        })
      })
      this.shareManager?.on('sendChange', (sendChangeData: SendChangeData) => {
        Log.info(TAG, `sendChange, data = ${JSON.stringify(sendChangeData)}`);
        this.sendChangeListeners.forEach(listener => {
          listener && listener(sendChangeData);
        })
      })
      this.shareManager?.on('authStateChange', (arg: { param: string }) => {
        Log.info(TAG, `authStateChange, data = ${JSON.stringify(arg)}`);
        let authStateMsgData: AuthStateMsgData = JSON.parse(arg.param);
        this.authStateChangeListeners.forEach(listener => {
          listener && listener(authStateMsgData);
        })
      })
    } catch (err) {
      Log.error(TAG, `on(register) err: ${err}, ${JSON.stringify(err)}`);
    }
    this.registered = true;
  }

  private unregisterListeners() {
    Log.info(TAG, 'unregisterListeners in');
    if (!this.registered) {
      Log.warn(TAG, 'not registered in unregisterListeners');
      return;
    }
    try {
      this.shareManager?.off('discoverSuccess');
      this.shareManager?.off('discoverFailure');
      this.shareManager?.off('connectionChange');
      this.shareManager?.off('sendChange');
      this.shareManager?.off('authStateChange');
    } catch (err) {
      Log.error(TAG, `off(unregister) err: ${err}, ${JSON.stringify(err)}`);
    }
    this.registered = false;
  }
}

const sShareManager: ShareManager = createOrGet(ShareManager, 'ShareManager');

export default sShareManager;